#include <linux/notifier.h>
#include <linux/smp.h>
#include <linux/cpu_pm.h>
#include <linux/cpuhotplug.h>
#include "coresight_priv.h"

static DEFINE_PER_CPU(CoresightDesc, coresight_desc);
static int coresight_pm_start = 0;

static int coresight_offline(u32 cpu)
{
	CoresightDesc *desc = per_cpu_ptr(&coresight_desc, cpu);

	desc->flag = CORESIGHT_SUSPEND;
	coresight_stop(desc);
	return 0;
}

static int coresight_online(u32 cpu)
{
	CoresightDesc *desc = per_cpu_ptr(&coresight_desc, cpu);

	desc->flag = CORESIGHT_RESUME;
	coresight_start(desc);
	return 0;
}

static void coresight_suspend(void)
{
	CoresightDesc *desc = this_cpu_ptr(&coresight_desc);

	desc->flag = CORESIGHT_SUSPEND;
	coresight_stop(desc);
}

static void coresight_resume(void)
{
	CoresightDesc *desc = this_cpu_ptr(&coresight_desc);

	desc->flag = CORESIGHT_RESUME;
	coresight_start(desc);
}

static int coresight_resume_notify(struct notifier_block *nb, unsigned long cmd,
				   void *v)
{
	if (!coresight_pm_start)
		return NOTIFY_DONE;

	switch (cmd) {
	case CPU_PM_EXIT:
	case CPU_PM_ENTER_FAILED:
		coresight_resume();
		break;
	default:
		return NOTIFY_DONE;
	}

	return NOTIFY_OK;
}

static int coresight_suspend_notify(struct notifier_block *nb,
				    unsigned long cmd, void *v)
{
	if (!coresight_pm_start)
		return NOTIFY_DONE;

	switch (cmd) {
	case CPU_PM_ENTER:
		coresight_suspend();
		break;
	default:
		return NOTIFY_DONE;
	}

	return NOTIFY_OK;
}

static struct notifier_block coresight_cpu_pm_resume = {
	.notifier_call = coresight_resume_notify,
	.priority = INT_MAX,
};

static struct notifier_block coresight_cpu_pm_suspend = {
	.notifier_call = coresight_suspend_notify,
	.priority = INT_MIN,
};

void coresight_disable_cpus(const struct cpumask *cpumask)
{
	int cpu;

	coresight_pm_start = 0;
	for_each_cpu(cpu, cpumask) {
		CoresightDesc *desc = per_cpu_ptr(&coresight_desc, cpu);
		desc->flag = CORESIGHT_STOP;
		smp_call_function_single(cpu, coresight_stop, desc, 1);
	}
}

int coresight_enable_cpus(const struct cpumask *cpumask)
{
	int cpu;
	struct cpumask enable_mask;

	for_each_cpu(cpu, cpumask) {
		CoresightDesc *desc = per_cpu_ptr(&coresight_desc, cpu);
		desc->flag = CORESIGHT_START;
		if (smp_call_function_single(cpu, coresight_start, desc, 1)) {
			coresight_disable_cpus(&enable_mask);
			return -1;
		}
		cpumask_set_cpu(cpu, &enable_mask);
	}
	coresight_pm_start = 1;

	return 0;
}

static int coresight_pm_init(void)
{
	int ret;

	ret = cpu_pm_register_notifier(&coresight_cpu_pm_suspend);
	if (ret)
		goto err3;
	ret = cpu_pm_register_notifier(&coresight_cpu_pm_resume);
	if (ret)
		goto err2;
	ret = cpuhp_setup_state_nocalls(CPUHP_AP_ARM_CORESIGHT_STARTING,
					"coresight/coresight4:starting",
					coresight_online, coresight_offline);
	if (ret)
		goto err1;

	return ret;

err1:
	cpu_pm_unregister_notifier(&coresight_cpu_pm_resume);
err2:
	cpu_pm_unregister_notifier(&coresight_cpu_pm_suspend);
err3:
	return ret;
}
static void coresight_pm_exit(void)
{
	cpuhp_remove_state_nocalls(CPUHP_AP_ARM_CORESIGHT_STARTING);
	cpu_pm_unregister_notifier(&coresight_cpu_pm_resume);
	cpu_pm_unregister_notifier(&coresight_cpu_pm_suspend);
}

int coresight_smp_init(void)
{
	return coresight_pm_init();
}

void coresight_smp_exit(void)
{
	coresight_pm_exit();
	if (coresight_pm_start)
		coresight_disable_cpus(cpu_possible_mask);
}
